package com.atilim.cargo.test;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

import com.atilim.cargo.model.Road;
import com.atilim.cargo.model.Vertex;
import com.atilim.cargo.util.Utility;

public class OpenStreetMap {

	public static List<Vertex> vertexes = new ArrayList<Vertex>();
	public static Set<Road> roads = new HashSet<Road>();
	public static List<Way> ways = new ArrayList<Way>();

	

	
	static void findPoint (){
		double x1 = 39.9184169;
		double y1 = 32.8911228;
		double x2 = 39.9185024;
		double y2 = 32.8916621;
		
		double m = (y2 - y1) / (x2 - x1);
		double b = y2 - (m * x2);
		
		//y = m * x + b;
		double xp = 39.918327418554185;
		double yp = 32.89139270782471;
		
		double x0 = ( (m * yp) + xp - (m * b) ) / ( m * m + 1 );
		double y0 = m * ( ( (m * yp) + xp - (m * b) ) / (m * m + 1) ) + b;
		
		System.out.println("slope: " + m);
		System.out.println("b: " + b);
		System.out.println("x0: " + x0);
		System.out.println("y0: " + y0);

	}
	
	static {
		parseXmlFile();
	}
	
	public static void parseXmlFile(){
		//get the factory
		DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();

		try {

			//Using factory get an instance of document builder
			DocumentBuilder db = dbf.newDocumentBuilder();

			//parse using builder to get DOM representation of the XML file
			org.w3c.dom.Document dom = db.parse("c:/map.osm");
			parseDocument(dom);
			
		} catch(Exception pce) {
			pce.printStackTrace();
		}
	}
	
	private static void parseDocument(org.w3c.dom.Document dom){
		//get the root element
		Element docEle = dom.getDocumentElement();

		//get a nodelist of elements named node
		NodeList nl = docEle.getElementsByTagName("node");
		if(nl != null && nl.getLength() > 0) {
			for(int i = 0 ; i < nl.getLength();i++) {

				//get the employee element
				Element el = (Element)nl.item(i);

				//get the Node object
				Vertex v = getVertex(el);
				//System.out.println(v);
				
				//add it to list
				vertexes.add(v);
			}
		}
		
		
		//get a nodelist of elements named way
		NodeList nlWay = docEle.getElementsByTagName("way");
		if(nlWay != null && nlWay.getLength() > 0) {
			for(int i = 0 ; i < nlWay.getLength();i++) {

				//get the way element
				Element el = (Element)nlWay.item(i);

				//get the Node object
				Way v = getWay(el);
				
				//add it to list
				ways.add(v);
			}
		}
		
		
		for (Way w : ways) {
			for (int i = 0; i < w.getNodes().size() ; i++) {
				if (i + 1 == w.getNodes().size()) {
					break;
				}
				Road r = new Road(w.getNodes().get(i), w.getNodes().get(i + 1));
				roads.add(r);
			}
		}

		//add links
		for (Vertex v: vertexes) {
			List<Vertex> links = getLinks (v);
			v.addLink(links.toArray(new Vertex [0] ));
		}
		/*
		System.out.println(vertexes.size());
		System.out.println(ways.size());
		System.out.println(roads.size());		
		*/
		System.out.println("Loaded");
	}
	
	private static List<Vertex> getLinks (Vertex v) {
		List<Vertex> links = new ArrayList<Vertex>();
		for (Way w : ways) {
			for (int i = 0; i < w.getNodes().size() ; i++) {
				Vertex vertex = w.getNodes().get(i);
				if (vertex.equals(v)) {
					if (i + 1 < w.getNodes().size()) {
						links.add(w.getNodes().get(i + 1));
					} 
					if (i > 0) {
						links.add(w.getNodes().get(i - 1));
					}
					break;
				}
			}
		}
		return links;
	}
	
	/**
	 * 
	 */
	private static Vertex getVertex(Element empEl) {

		//for each <node> element
		//id, lat, lon
		String id = getAttributeValue(empEl,"id");
		double lat = getDoubleValue(empEl, "lat");
		double lon = getDoubleValue(empEl, "lon");

		//Create a new Vertex with the values read from the xml nodes
		Vertex v = new Vertex(id, Double.toString(lat), Double.toString(lon));

		return v;
	}
	
	private static Way getWay(Element empEl) {
		
		Way way = new Way();
		
		//for each <way> element
		way.setId(empEl.getAttribute("id"));
		List<Vertex> nodes = new ArrayList<Vertex>();
		NodeList nlNd = empEl.getElementsByTagName("nd");
		if(nlNd != null && nlNd.getLength() > 0) {
			for(int i = 0 ; i < nlNd.getLength();i++) {
				
				//get the way element
				Element el = (Element)nlNd.item(i);

				String ref = el.getAttribute("ref");
				Vertex v = getByName(ref);
				//add it to list
				nodes.add(v);
			}
		}
		way.setNodes(nodes);
		return way;
	}
	
	public static Vertex getByName (String name) {
		for (Vertex v: vertexes) {
			if (v.getName().equals(name)) {
				return v;
			}
		}
		return null;
	}


	/**
	 * I take a xml element and the tag name, look for the tag and get
	 * the text content
	 * i.e for <employee><name>John</name></employee> xml snippet if
	 * the Element points to employee node and tagName is 'name' I will return John
	 */
	private String getTextValue(Element ele, String tagName) {
		String textVal = null;
		NodeList nl = ele.getElementsByTagName(tagName);
		if(nl != null && nl.getLength() > 0) {
			Element el = (Element)nl.item(0);
			textVal = el.getFirstChild().getNodeValue();
		}

		return textVal;
	}
	
	private static String getAttributeValue(Element ele, String attrName) {
		return  ele.getAttribute(attrName);
	}

	
	private static double getDoubleValue(Element ele, String tagName) {
		//in production application you would catch the exception
		return Double.parseDouble(getAttributeValue(ele,tagName));
	}
	
	/**
	 * lat lng değerlerine en yakın vertex<br>
	 * tıklanan noktaya en yakın vertex bulunuyor.
	 * 
	 * @param lat
	 * @param lng
	 * @return
	 */
	public static Vertex findClosest (double lat, double lng) {
		Vertex closest = null;
		double dist = 0.0;
		Vertex nokta = new Vertex("", Double.toString(lat), 
				Double.toString(lng));
		for (Vertex v : vertexes) {
			double distTmp = Math.abs(Utility.distance(v, nokta));
			if (closest == null) {
				closest = v;
				dist = distTmp;
				continue;
			}
			if (distTmp < dist) {
				dist = distTmp;
				closest = v;
			}
		}
		return closest;
	}
	
	/**
	 * En yakın noktaya bağlı yollardan, tıklanan noktaya en yakın olan yol bulunuyor.
	 * 
	 * @param lat
	 * @param lng
	 * @return
	 */
	public static Road findClosestRoad (Vertex closest, double lat, double lng) {
		
		Vertex tiklananNokta = new Vertex("" , Double.toString(lat), Double.toString(lng));
		
		//new OpenStreetMap().parseXmlFile();
		
		System.out.println("Closest: " + closest);
		
		double tmpAngle = 0.0;
		Vertex foundVertex = null;
		for (Vertex link: closest.getLinks()) {
			System.out.println("Link " + link);
			double a = Utility.distance(tiklananNokta, closest);
			double b = Utility.distance(tiklananNokta, link);
			double c = Utility.distance(closest, link);
			
			double angle = Math.acos( ( (a*a) + (b*b) - (c*c) ) / (2 * a * b) );
			
			if (tmpAngle < angle) {
				tmpAngle = angle;
				foundVertex = link;
			}
			
			System.out.println(a);
			System.out.println(b);
			System.out.println(c);
			System.out.println("Angle: " + angle);
			System.out.println(" ----- ");
		}
		System.out.println("Vertex: " + closest);
		return new Road(closest, foundVertex);
	}
	
	/**
	 * Yola dik inildiğinde bulunan nokta
	 * 
	 * @param tiklanan
	 * @param road
	 * @return
	 */
	public static Vertex findPointOnRoad (double lat, double lng, Road road) {
		
		Vertex tiklanan = new Vertex("", Double.toString(lat), Double.toString(lng));
		
		double x1 = Double.parseDouble(road.getV1().getLat());
		double y1 = Double.parseDouble(road.getV1().getLon());
		double x2 = Double.parseDouble(road.getV2().getLat());
		double y2 = Double.parseDouble(road.getV2().getLon());
		
		double m = (y2 - y1) / (x2 - x1);
		double b = y2 - (m * x2);
		
		//y = m * x + b; tıklanan nokta
		double xp = Double.parseDouble(tiklanan.getLat());
		double yp = Double.parseDouble(tiklanan.getLon());
		
		double x0 = ( (m * yp) + xp - (m * b) ) / ( m * m + 1 );
		double y0 = m * ( ( (m * yp) + xp - (m * b) ) / (m * m + 1) ) + b;
		
		System.out.println("slope: " + m);
		System.out.println("b: " + b);
		System.out.println("x0: " + x0);
		System.out.println("y0: " + y0);
		
		return new Vertex("", Double.toString(x0), Double.toString(y0));
	}
}
